/**
 * 开发环境构建脚本
 *
 * 此脚本使用 esbuild 进行快速的开发环境构建，提供更快的构建速度和文件监听功能。
 * 虽然开发环境使用 esbuild，但生产环境仍使用 Rollup，因为 Rollup 能生成更小的文件
 * 并提供更好的 Tree-shaking 效果。
 *
 * 主要特性：
 * - 使用 esbuild 实现快速构建和热重载
 * - 支持多种输出格式（global、cjs、esm）
 * - 自动处理外部依赖和 Node.js polyfill
 * - 配置开发环境特定的全局常量
 * - 支持 watch 模式进行实时开发
 *
 * @ts-check
 */

import esbuild from 'esbuild'
import fs from 'node:fs'
import { dirname, relative, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createRequire } from 'node:module'
import { parseArgs } from 'node:util'
import { polyfillNode } from 'esbuild-plugin-polyfill-node'

// 创建 require 函数以在 ES 模块中使用 CommonJS 模块
const require = createRequire(import.meta.url)
// 获取当前文件所在目录的路径
const __dirname = dirname(fileURLToPath(import.meta.url))

/**
 * 解析命令行参数
 * 支持的参数：
 * - format: 输出格式（global, cjs, esm-bundler, esm-browser, global-runtime 等）
 * - prod: 是否构建生产版本（会影响 __DEV__ 等全局常量）
 * - inline: 是否内联所有依赖（不使用 external）
 */
const {
  values: { format: rawFormat, prod, inline: inlineDeps },
  positionals, // 位置参数，用于指定要构建的包
} = parseArgs({
  allowPositionals: true,
  options: {
    format: {
      type: 'string',
      short: 'f',
      default: 'global', // 默认构建全局格式（IIFE）
    },
    prod: {
      type: 'boolean',
      short: 'p',
      default: false, // 默认构建开发版本
    },
    inline: {
      type: 'boolean',
      short: 'i',
      default: false, // 默认不内联依赖
    },
  },
})

// 确定最终的输出格式
const format = rawFormat || 'global'
// 确定要构建的目标包，如果没有指定则默认构建 vue
const targets = positionals.length ? positionals : ['vue']

/**
 * 根据 format 参数确定 esbuild 的输出格式
 * - global: 转换为 IIFE（立即执行函数表达式）
 * - cjs: 保持 CommonJS 格式
 * - 其他: 使用 ES 模块格式
 */
const outputFormat = format.startsWith('global')
  ? 'iife'
  : format === 'cjs'
    ? 'cjs'
    : 'esm'

/**
 * 生成输出文件名的后缀
 * 如果格式以 '-runtime' 结尾，则生成 runtime.xxx 格式的后缀
 * 否则直接使用格式名作为后缀
 */
const postfix = format.endsWith('-runtime')
  ? `runtime.${format.replace(/-runtime$/, '')}`
  : format

// 读取私有包目录列表，用于判断包是否为私有包
const privatePackages = fs.readdirSync('packages-private')

/**
 * 逐个构建每个目标包
 * 每个包都会创建一个 esbuild context 并启动 watch 模式
 */
for (const target of targets) {
  // 判断包类型：私有包或公开包
  const pkgBase = privatePackages.includes(target)
    ? `packages-private` // 私有包目录
    : `packages` // 公开包目录

  const pkgBasePath = `../${pkgBase}/${target}`
  // 读取包配置信息
  const pkg = require(`${pkgBasePath}/package.json`)

  /**
   * 生成输出文件路径
   * 格式：{target}.{postfix}.{prod ? 'prod.' : ''}js
   * vue-compat 包特殊处理：输出为 vue.xxx.js
   */
  const outfile = resolve(
    __dirname,
    `${pkgBasePath}/dist/${
      target === 'vue-compat' ? `vue` : target
    }.${postfix}.${prod ? `prod.` : ``}js`,
  )
  // 生成相对路径用于日志输出
  const relativeOutfile = relative(process.cwd(), outfile)

  /**
   * 处理外部依赖（external dependencies）
   * 此逻辑大部分从 rollup.config.js 复制而来
   * TODO: 应该抽取出通用逻辑避免代码重复
   */
  /** @type {string[]} 存储外部依赖列表 */
  let external = []

  if (!inlineDeps) {
    // 如果不内联依赖
    /**
     * 对于 CommonJS 和 ESM bundler 格式，将所有依赖设为外部依赖
     * 这样可以减小构建产物大小，让打包工具处理依赖解析
     */
    if (format === 'cjs' || format.includes('esm-bundler')) {
      external = [
        ...external,
        ...Object.keys(pkg.dependencies || {}), // 生产依赖
        ...Object.keys(pkg.peerDependencies || {}), // 同级依赖
        // 为 @vue/compiler-sfc 和 server-renderer 添加 Node.js 内置模块
        'path',
        'url',
        'stream',
      ]
    }

    /**
     * 为 compiler-sfc 包添加特殊的外部依赖
     * 这些依赖主要是各种模板引擎和工具库
     */
    if (target === 'compiler-sfc') {
      // 解析 @vue/consolidate 的包路径
      const consolidatePkgPath = require.resolve(
        '@vue/consolidate/package.json',
        {
          paths: [resolve(__dirname, `../packages/${target}/`)],
        },
      )
      // 获取 consolidate 的开发依赖
      const consolidateDeps = Object.keys(
        require(consolidatePkgPath).devDependencies,
      )

      external = [
        ...external,
        ...consolidateDeps, // consolidate 的开发依赖
        // Node.js 内置模块
        'fs',
        'vm',
        'crypto',
        // 各种模板引擎相关依赖
        'react-dom/server',
        'teacup/lib/express',
        'arc-templates/dist/es5',
        'then-pug',
        'then-jade',
      ]
    }
  }
  /**
   * 配置 esbuild 插件
   * @type {Array<import('esbuild').Plugin>}
   */
  const plugins = [
    {
      name: 'log-rebuild',
      setup(build) {
        // 在每次构建完成后输出日志
        build.onEnd(() => {
          console.log(`built: ${relativeOutfile}`)
        })
      },
    },
  ]

  /**
   * 如果不是 CommonJS 格式且包支持非浏览器环境，添加 Node.js polyfill
   * 这对于需要在浏览器中使用 Node.js 内置模块的包很有用
   */
  if (format !== 'cjs' && pkg.buildOptions?.enableNonBrowserBranches) {
    plugins.push(polyfillNode())
  }

  /**
   * 创建 esbuild context 并启动 watch 模式
   * context 允许我们启动持续的 watch 模式进行实时开发
   */
  esbuild
    .context({
      // 入口文件路径
      entryPoints: [resolve(__dirname, `${pkgBasePath}/src/index.ts`)],
      // 输出文件路径
      outfile,
      // 开启打包模式
      bundle: true,
      // 外部依赖列表
      external,
      // 生成 source map 以便调试
      sourcemap: true,
      // 输出格式（iife/cjs/esm）
      format: outputFormat,
      // 全局变量名（仅对 IIFE 格式有效）
      globalName: pkg.buildOptions?.name,
      // 目标平台：Node.js 或浏览器
      platform: format === 'cjs' ? 'node' : 'browser',
      // 插件列表
      plugins,
      /**
       * 全局常量定义
       * 这些常量在构建时会被替换为实际值，用于条件编译和特性控制
       */
      define: {
        __COMMIT__: `"dev"`, // Git 提交哈希（开发模式固定为 "dev"）
        __VERSION__: `"${pkg.version}"`, // 包版本号
        __DEV__: prod ? `false` : `true`, // 是否为开发模式
        __TEST__: `false`, // 是否为测试模式
        // 是否为浏览器环境（非 CommonJS 且未启用非浏览器分支）
        __BROWSER__: String(
          format !== 'cjs' && !pkg.buildOptions?.enableNonBrowserBranches,
        ),
        __GLOBAL__: String(format === 'global'), // 是否为全局格式
        __ESM_BUNDLER__: String(format.includes('esm-bundler')), // 是否为 ESM bundler 格式
        __ESM_BROWSER__: String(format.includes('esm-browser')), // 是否为 ESM browser 格式
        __CJS__: String(format === 'cjs'), // 是否为 CommonJS 格式
        __SSR__: String(format !== 'global'), // 是否支持服务端渲染
        __COMPAT__: String(target === 'vue-compat'), // 是否为兼容模式
        // Vue 特性开关
        __FEATURE_SUSPENSE__: `true`, // 启用 Suspense 特性
        __FEATURE_OPTIONS_API__: `true`, // 启用 Options API
        __FEATURE_PROD_DEVTOOLS__: `false`, // 生产环境开发工具支持
        __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: `true`, // 生产环境水化不匹配详情
      },
    })
    .then(ctx => ctx.watch()) // 启动文件监听模式
}
